﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Quail.Helpers
{
    /// <summary>
    /// A bunch of helpful random string and number methods to be used during tests
    /// </summary>
    public static class RandomStuff
    {
        private static readonly Random RandomizerInstance = new Random();

        private static string _defaultRandomEmailDomain;

        /// <summary>
        /// Gets the randomizer object
        /// </summary>
        public static Random Randomizer
        {
            get
            {
                return RandomizerInstance;
            }
        }

        /// <summary>
        /// Gets a random bool value
        /// </summary>
        /// <returns>true or false</returns>
        public static bool RandomBool()
        {
            return RandomNumber(0, 1) == 1;
        }

        /// <summary>
        /// Returns a random number within low and high values
        /// </summary>
        /// <param name="low">The lowest possible random number</param>
        /// <param name="high">The highest possible random number</param>
        /// <returns>A random integer</returns>
        public static int RandomNumber(int low, int high)
        {
            return Randomizer.Next(low, high + 1);
        }

        /// <summary>
        /// Returns a random number as a string, whatever length you specify
        /// Used for generating general ids or codes, like document number, or other various things
        /// </summary>
        /// <param name="lengthOfNumber">Length of the number (as a string) to return</param>
        /// <returns>A random number as a string</returns>
        public static string RandomDigits(int lengthOfNumber)
        {
            var result = string.Empty;
            for (var i = 0; i < lengthOfNumber; i++)
                result += Convert.ToChar(RandomNumber(48, 57));

            return result;
        }

        /// <summary>
        /// Returns a random floating-point value within low and high values.  Only up to 2 decimal points are returned.
        /// </summary>
        /// <param name="low">The lowest possible currency value</param>
        /// <param name="high">The highest possible currency value</param>
        /// <returns>A random floating point</returns>
        public static double RandomCurrency(double low, double high)
        {
            var dollarfigure = (double)RandomNumber((int)Math.Floor(low), (int)Math.Floor(high));

            if (dollarfigure == high)
                return dollarfigure;

            var decimals = RandomNumber(0, 99) / 100.0;

            dollarfigure += decimals;

            return dollarfigure;
        }

        /// <summary>
        /// Returns a random street address
        /// </summary>
        /// <returns>A string address</returns>
        public static string RandomStreetAddress()
        {
            return RandomNumber(100, 999) + " " + RandomRealWord() + " " + RandomStreetWord();
        }

        /// <summary>
        /// Creates a 12 character word
        /// </summary>
        /// <returns>A word with 12 random characters</returns>
        public static string RandomString()
        {
            return Guid.NewGuid().ToString().Substring(24);
        }

        /// <summary>
        /// Creates a random sentence with a number of words in it.  Each word has between 3 and 8 characters
        /// </summary>
        /// <param name="minwords">lowest number of words</param>
        /// <param name="maxwords">maxiumum number of words</param>
        /// <returns>A string with at least that many words in it</returns>
        public static string RandomSentence(int minwords, int maxwords)
        {
            return RandomSentence(minwords, maxwords, true);
        }

        /// <summary>
        /// Creates a random sentence with a number of words in it, but does not contain special characters.  Each word has between 3 and 8 characters
        /// </summary>
        /// <param name="minwords">lowest number of words</param>
        /// <param name="maxwords">maxiumum number of words</param>
        /// <returns>A string with at least that many words in it</returns>
        public static string RandomSentenceWithoutPunctuation(int minwords, int maxwords)
        {
            return RandomSentence(minwords, maxwords, false);
        }

        private static string RandomSentence(int minwords, int maxwords, bool includePunctuation)
        {
            var result = string.Empty;
            var totwords = RandomNumber(minwords, maxwords);
            const string randompuncuationmarks = "!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?`~";

            for (var word = 0; word < totwords; word++)
            {
                if (result.Length > 0)
                    result += " ";

                var theword = RandomWord(3, 8);

                if (includePunctuation)
                {
                    if (RandomNumber(1, 5) == 1)
                    {
                        // insert random character
                        var character = randompuncuationmarks.GetRandomCharacter();

                        theword = theword.Insert(RandomNumber(0, theword.Length), character.ToString());
                    }
                }

                result += theword;
            }

            return LeadingCaps(result) + ".";
        }

        /// <summary>
        /// Creates a completely random phone number, excluding area codes 555, 800, 888 and 900
        /// </summary>
        /// <param name="addLeadingOne">Add a leading 1</param>
        /// <param name="separator">Dash, dot or other separator to use between components of the phone number</param>
        /// <returns>A 10 or 11 digit number</returns>
        public static string RandomPhone(bool addLeadingOne = true, string separator = "")
        {
            string areacode;
            string exchange;

            for (; ; )
            {
                areacode = RandomAreaCode();
                exchange = RandomExchange();

                if (areacode != "800" && areacode != "900" && areacode != "555" && areacode != "888" && exchange != "555")
                    break;
            }

            return (addLeadingOne ? "1" + separator : string.Empty) + areacode + separator + exchange + separator + RandomNumber(1000, 9999);
        }

        /// <summary>
        /// Creates a completely random email address
        /// </summary>
        /// <returns>A fake email address beginning with "fake" and ending with ".com"</returns>
        public static string RandomEmail()
        {
            return RandomEmail("fake");
        }

        /// <summary>
        /// Creates a completely random email address
        /// </summary>
        /// <param name="strPrefix">Prefix for the email address</param>
        /// <returns>A fake email address beginning with the specified prefix and ending with ".com"</returns>
        public static string RandomEmail(string strPrefix)
        {
            return RandomEmail(strPrefix, DefaultRandomEmailDomain);
        }

        /// <summary>
        /// Creates a completely random email address
        /// </summary>
        /// <param name="strPrefix">Prefix for the email address</param>
        /// <param name="strDomain">The domain name for the email address</param>
        /// <returns>A fake email address beginning with the specified prefix and ending with the specified domain</returns>
        public static string RandomEmail(string strPrefix, string strDomain)
        {
            var now = DateTime.Now;
            return strPrefix + now.ToString("yyyyMMdd.HHmmss.ff") + RandomWord(2) + "@" + strDomain;
        }

        /// <summary>
        /// Gets the default random email domain.  Default value:  random??.com  where ?? are two random letters.
        /// </summary>
        public static string DefaultRandomEmailDomain
        {
            get
            {
                if (_defaultRandomEmailDomain == null)
                    return "random" + RandomWord(2).ToLower() + ".com";

                return _defaultRandomEmailDomain;
            }

            set
            {
                _defaultRandomEmailDomain = value;
            }
        }

        /// <summary>
        /// Creates a word with random characters
        /// </summary>
        /// <param name="length">The length of the word to create</param>
        /// <returns>A string of random alphabetic characters</returns>
        public static string RandomWord(int length)
        {
            var result = string.Empty;
            for (var i = 0; i < length; i++)
                result += Convert.ToChar(RandomNumber(65, 90));

            return LeadingCaps(result);
        }

        /// <summary>
        /// Creates a word with random characters
        /// </summary>
        /// <param name="minLength">The minimum length of the word</param>
        /// <param name="maxLength">The maximum length of the word</param>
        /// <returns>A string of random alphabetic characters whose length is at least minLength and at most maxLength</returns>
        public static string RandomWord(int minLength, int maxLength)
        {
            return RandomWord(Randomizer.Next(minLength, maxLength));
        }

        /// <summary>
        /// Returns the string parameter such that the first character is uppercase and all other characters are lowercase
        /// </summary>
        /// <param name="str">The string</param>
        /// <returns>The input string with the first letter uppercase</returns>
        public static string LeadingCaps(string str)
        {
            if (str.Length == 0)
                return str;
            if (str.Length == 1)
                return str.ToUpper();

            return str.Substring(0, 1).ToUpper() + str.Substring(1).ToLower();
        }

        /// <summary>
        /// Creates a random SSN
        /// </summary>
        /// <returns>A random, invalid SSN so as to protect the innocent.</returns>
        public static string RandomSSN()
        {
            var result = new string[] { "000", "00", "0000" };

            for (var i = 0; i < 3; i++)
            {
                result[i] = RandomNumber(1, Convert.ToInt32("8".PadLeft(result[i].Length, '8'))).ToString().PadLeft(result[i].Length, '0');
            }

            return string.Join("-", result);
        }

        /// <summary>
        /// Return a random 5 digit number in the range for a zip code. 
        /// </summary>
        /// <returns></returns>
        public static int RandomZipCode()
        {
            return RandomNumber(10000, 99999);
        }

        /// <summary>
        /// Returns a random enum of Type TEnum
        /// </summary>
        /// <typeparam name="TEnum">The type of the enum.</typeparam>
        /// <returns></returns>
        public static TEnum RandomEnum<TEnum>()
        {
            return ((IEnumerable<TEnum>)Enum.GetValues(typeof(TEnum))).RandomItem();
        }

        /// <summary>
        /// Returns a random area code that complies with NANP standards
        /// </summary>
        /// <returns>
        /// The int area code
        /// </returns>
        public static string RandomAreaCode()
        {
            int f, s, t;
            for (; ; )
            {
                f = RandomNumber(2, 9);
                s = RandomNumber(0, 8);
                t = RandomNumber(0, 9);

                if (PhoneNumberValidation.IsAreaCodeValid(f, s, t))
                    break;
            }

            return string.Format("{0}{1}{2}", f, s, t);
        }

        /// <summary>
        /// Returns a random exchange that (somewhat) complies with NANP standards.
        /// </summary>
        /// <returns>
        /// The int exchange code
        /// </returns>
        /// <remarks>NANP rules about 555 numbers are not applied because RandomStuff.RandomPhone() already excludes the entire 555 exchange.</remarks>
        public static string RandomExchange()
        {
            int f, s, t;
            for (; ; )
            {
                f = RandomNumber(2, 9);
                s = RandomNumber(0, 9);
                t = RandomNumber(0, 9);

                if (PhoneNumberValidation.IsExchangeValid(f, s, t))
                    break;
            }

            return string.Format("{0}{1}{2}", f, s, t);
        }

        /// <summary>
        /// Gets a random First Name from a list of "nice" names
        /// </summary>
        /// <returns></returns>
        public static string RandomFirstName()
        {
            return RandomStuffNames.FirstNames.RandomItem();
        }

        /// <summary>
        /// Gets a random Last Name from a list of "nice" names
        /// </summary>
        /// <returns></returns>
        public static string RandomLastName()
        {
            return RandomStuffNames.LastNames.RandomItem();
        }

        ///<summary>
        /// Gets a random street word, to use in addresses
        ///</summary>
        ///<returns></returns>
        public static string RandomStreetWord()
        {
            return RandomStuffWords.RandomStreetWords.RandomItem();
        }

        ///<summary>
        /// Gets a random school word, to use for education fillouts
        ///</summary>
        ///<returns></returns>
        public static string RandomSchoolWord()
        {
            return RandomStuffWords.RandomSchoolWords.RandomItem();
        }

        ///<summary>
        /// Gets a random real word from a list of "pretty" words
        ///</summary>
        ///<returns></returns>
        public static string RandomRealWord()
        {
            return RandomStuffWords.RandomNouns.RandomItem();
        }

        ///<summary>
        /// Gets a sentence made of random nouns
        ///</summary>
        ///<param name="minwords"></param>
        ///<param name="maxwords"></param>
        ///<returns></returns>
        public static string RandomRealSentence(int minwords, int maxwords)
        {
            var result = string.Empty;
            var totwords = RandomNumber(minwords, maxwords);
            
            for (var word = 0; word < totwords; word++)
            {
                if (result.Length > 0)
                    result += " ";

                var theword = RandomRealWord();
                result += theword;
            }

            return LeadingCaps(result) + ".";
        }

        #region States

        /// <summary>
        /// Gets a random state abbreviation.
        /// </summary>
        /// <returns></returns>
        public static string RandomState()
        {
            return RandomState(false, false, false);
        }

        /// <summary>
        /// Gets a random state name
        /// </summary>
        /// <returns></returns>
        public static string RandomState(bool fullName)
        {
            return RandomState(fullName, false, false);
        }

        /// <summary>
        /// Gets a random state, includeing Fullname, DC and IA options.
        /// </summary>
        /// <returns></returns>
        public static string RandomState(bool fullName, bool includeFederalDistrict, bool includeInsularArea)
        {
            var randomState = RandomStuffStates.States
                .Where(s=>
                    (s.Type == State.StateTerritoryType.State) ||
                    (s.Type == State.StateTerritoryType.FederalDistrict && includeFederalDistrict) ||
                    (s.Type == State.StateTerritoryType.InsularArea && includeInsularArea))
                .RandomItem();

            return fullName ? randomState.StateName : randomState.StateAbbreviation;
        }

        #endregion
    }
}
